Skip to content

feat: add live screen recording preview#319

Open
hamidlabs wants to merge 9 commits intosiddharthvaddem:mainfrom
hamidlabs:feature/live-preview
Open

feat: add live screen recording preview#319
hamidlabs wants to merge 9 commits intosiddharthvaddem:mainfrom
hamidlabs:feature/live-preview

Conversation

@hamidlabs
Copy link
Copy Markdown

@hamidlabs hamidlabs commented Apr 4, 2026

Summary

Adds a real-time live preview to the launch window, similar to OBS Studio's preview panel. Users can now see exactly what will be recorded before and during recording.

  • Live preview window: Replaces the 500x155 HUD pill bar with a 480x420 resizable preview window showing a real-time screen capture feed
  • Canvas compositor: Renders screen capture + circular webcam PiP overlay at 30fps using a throttled requestAnimationFrame loop, capped at 960px internal resolution for GPU efficiency
  • Stream handoff: Preview stream is seamlessly handed off to MediaRecorder when recording starts — no double getUserMedia calls, no Wayland PipeWire re-prompts
  • Linux PipeWire/Wayland support: Enables WebRTCPipeWireCapturer and ozone-platform-hint flags, adds session type detection IPC for Wayland-aware behavior

Changes

File Change
electron/main.ts PipeWire/Wayland flags for Linux
electron/windows.ts Resized HUD overlay to 480x420, resizable, bottom-right
electron/ipc/handlers.ts get-session-type IPC handler
electron/preload.ts Exposed getSessionType to renderer
electron/electron-env.d.ts Type declaration for new IPC
src/vite-env.d.ts Type declaration for new IPC
src/hooks/usePreviewStream.ts New — preview stream lifecycle management
src/hooks/useScreenRecorder.ts Accept PreviewStreamHandoff to reuse preview streams
src/components/launch/LivePreview.tsx New — canvas-based preview with webcam PiP
src/components/launch/LaunchWindow.tsx Redesigned with integrated live preview

Test plan

  • Select a screen source → live preview should start automatically
  • Toggle webcam → circular PiP overlay appears/disappears in preview
  • Click REC → recording starts using the preview stream (no second permission prompt)
  • Stop recording → preview restarts after brief delay
  • Resize the preview window (380-640px width range)
  • Test on Wayland session (PipeWire screen capture)
  • Test on X11 session (desktopCapturer source enumeration)

Summary by CodeRabbit

  • New Features

    • Live canvas-based screen preview with optional webcam picture-in-picture
    • Automatic session-type detection (exposed to the app) to improve display capture on Linux
    • Linux startup tuned to better support PipeWire-based screen capture
  • Refactor

    • HUD redesigned: top title bar, dedicated preview region, repositioned controls, resizable bounds, and taskbar visibility
    • Recording flow refactored to reuse preview streams and streamline preview/record toggles

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

Adds Linux session-type detection and preload IPC; enables PipeWire capture flags on Linux; introduces a canvas-based live preview + preview lifecycle hook; extends recorder to accept preview handoff; refactors LaunchWindow to integrate preview and updates HUD overlay sizing/positioning.

Changes

Cohort / File(s) Summary
Session Type Detection & Preload API
electron/electron-env.d.ts, electron/preload.ts, electron/ipc/handlers.ts, src/vite-env.d.ts
Added ipcMain.handle("get-session-type", ...) and window.electronAPI.getSessionType(): Promise<string> bridge; handler returns platform session type (Linux: XDG_SESSION_TYPE / WAYLAND_DISPLAY inference; non-Linux: "x11").
Linux PipeWire / Startup Flags
electron/main.ts
On Linux, adds --enable-features=WebRTCPipeWireCapturer and --ozone-platform-hint=auto startup flags for PipeWire/Wayland capture.
HUD Overlay Window Layout
electron/windows.ts
Changed HUD overlay size/constraints and anchoring (fixed → resizable 480×420, min/max bounds), enabled resizing and taskbar visibility, updated bottom-right positioning and padding.
Preview Lifecycle Hook
src/hooks/usePreviewStream.ts
New hook managing preview start/stop, optional webcam, detach helpers, streams/sourceId state, and cleanup.
Live Preview Component
src/components/launch/LivePreview.tsx
New canvas-driven component to render screen (+ webcam PiP) at ~30 FPS using hidden video elements, with resizing, drawing loop, and teardown.
Recording Handoff Integration
src/hooks/useScreenRecorder.ts
Added PreviewStreamHandoff type and updated API to accept preview handoff; reuses preview streams when provided, adjusts audio capture and appliesConstraints behavior.
Launch Window Refactor & UI
src/components/launch/LaunchWindow.tsx
Large refactor: integrates usePreviewStream and LivePreview, polling to auto-start preview on source change, new recording toggle flow that detaches and hands off preview streams, UI layout rework, and callback/effect refactors.
Type Declarations
src/vite-env.d.ts, electron/electron-env.d.ts
Added getSessionType: () => Promise<string> to the Window.electronAPI declarations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • siddharthvaddem

Poem


the canvas hums, the videos dance in frames,
wayland whispers, x11 nods by name,
a preview is born, then handed off with care,
the HUD slips to the corner — lowkey debonair,
capture hops, piping streams through rabbit-hole flair 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title 'feat: add live screen recording preview' clearly and concisely summarizes the main feature addition, matching the primary objective of introducing a real-time live preview to the launch window.
Description check ✅ Passed Description covers purpose, key changes with file-level breakdown, and a comprehensive test plan checklist. Follows template structure well but omits explicit Motivation and Type of Change sections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c69debc1fe

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
electron/ipc/handlers.ts (1)

221-224: Consider normalizing session type to known values.

The handler returns process.env.XDG_SESSION_TYPE directly, which could contain unexpected values beyond "wayland" or "x11" (e.g., "tty", "mir", or custom values). If the consumer code expects only "wayland" or "x11", consider normalizing:

♻️ Optional: Normalize to known session types
 ipcMain.handle("get-session-type", () => {
   if (process.platform !== "linux") return "x11";
-  return process.env.XDG_SESSION_TYPE || (process.env.WAYLAND_DISPLAY ? "wayland" : "x11");
+  const sessionType = process.env.XDG_SESSION_TYPE?.toLowerCase();
+  if (sessionType === "wayland") return "wayland";
+  if (sessionType === "x11") return "x11";
+  // Fallback detection
+  return process.env.WAYLAND_DISPLAY ? "wayland" : "x11";
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 221 - 224, The get-session-type IPC
handler returns process.env.XDG_SESSION_TYPE directly which may be unexpected
values; update the ipcMain.handle("get-session-type", ...) implementation to
normalize the session type to only "wayland" or "x11": read XDG_SESSION_TYPE
(and fall back to checking WAYLAND_DISPLAY as before), lowercase it, then return
"wayland" if it equals "wayland" and return "x11" for any other value. Ensure
you update the logic inside the ipcMain.handle callback so consumers always
receive one of the two known values.
src/components/launch/LaunchWindow.tsx (1)

145-172: prevSourceId resets when effect dependencies change, potentially causing redundant preview starts.

The prevSourceId variable is declared inside the effect, so it resets to null whenever the effect re-runs due to dependency changes (recording or startPreview). This means when recording stops, the effect restarts, prevSourceId becomes null, and startPreview may be called even for the same source.

♻️ Use useRef to persist prevSourceId across effect runs
+const prevSourceIdRef = useRef<string | null>(null);

 // Poll for source selection and start preview when source is picked
 useEffect(() => {
-  let prevSourceId: string | null = null;

   const checkSelectedSource = async () => {
     if (window.electronAPI) {
       const source = await window.electronAPI.getSelectedSource();
       if (source) {
         setSelectedSource(source.name);
         setHasSelectedSource(true);

         // Auto-start preview when source changes
-        if (source.id !== prevSourceId && !recording) {
-          prevSourceId = source.id;
+        if (source.id !== prevSourceIdRef.current && !recording) {
+          prevSourceIdRef.current = source.id;
           startPreview(source.id);
         }
       } else {
         setSelectedSource("Screen");
         setHasSelectedSource(false);
       }
     }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/LaunchWindow.tsx` around lines 145 - 172, prevSourceId
is declared inside the useEffect so it resets whenever the effect re-runs
causing duplicate startPreview calls; move that state to a ref (e.g.,
prevSourceIdRef = useRef<string | null>(null)) at component scope and replace
uses of prevSourceId in the effect with prevSourceIdRef.current, updating
prevSourceIdRef.current = source.id after calling startPreview(source.id) and
when selecting a source so the previous source id persists across effect runs;
keep the rest of the checkSelectedSource logic and the interval handling the
same.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 180-213: The setTimeout inside handleToggleRecording can fire
after unmount; add a ref (e.g., restartPreviewTimeoutRef) to store the timeout
id when scheduling the 500ms restart and use that ref to clearTimeout if needed,
update the callback to assign the timeout to restartPreviewTimeoutRef.current
instead of an anonymous timeout, and add a cleanup useEffect that clears
restartPreviewTimeoutRef.current on unmount to prevent calling startPreview
after the component has been unmounted.

In `@src/components/launch/LivePreview.tsx`:
- Around line 151-163: The effect in LivePreview that creates a video element
when streams.webcam becomes available can leak resources; update the useEffect
(the one that checks webcamVideoRef.current and streams?.webcam) to return a
cleanup function that tears down the created element: if webcamVideoRef.current
exists and its srcObject equals the stream, pause the video, remove it from the
DOM, set webcamVideoRef.current.srcObject = null, stop any tracks on the
MediaStream, and clear webcamVideoRef.current; this ensures toggling the webcam
or unmounting releases streams and DOM nodes even when the parent streams object
identity does not change.
- Around line 105-121: The crop math uses webcamVideo.videoWidth/height and can
divide by zero before metadata loads; inside the drawing routine in
LivePreview.tsx where ww, wh, aspectRatio, sx, sy, sw, sh are computed, guard by
checking webcamVideo.videoWidth and webcamVideo.videoHeight > 0 and bail out or
skip cropping/drawing until they are valid (or set safe defaults like 1) so
aspectRatio is never Infinity/NaN and drawImage gets valid sx/sy/sw/sh values.

In `@src/hooks/usePreviewStream.ts`:
- Around line 24-35: The preview startup/stop code must serialize and abort
stale webcam requests to avoid double prompts and leaked tracks: add a
latestRequestRef (useRef<symbol|null>) and, in startPreview and in the effect
that also calls getUserMedia, create a new unique symbol and assign it to
latestRequestRef before awaiting navigator.mediaDevices.getUserMedia; after the
await verify the symbol still matches latestRequestRef.current and only then
assign webcamStreamRef.current and setStreams; if it does not match, stop the
newly obtained tracks immediately; update stopPreview to clear
latestRequestRef.current (set to null) so late resolves know the request was
cancelled and always stop any stream returned by stale requests.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 453-456: When reusing the preview handoff stream in
useScreenRecorder, the preview track must be upgraded to recording constraints
instead of using the preview resolution as-is; locate the branch that sets
webcamStream.current = previewHandoff.webcamStream and before assigning or
before starting the recorder call applyConstraints on the preview video track
(the track from previewHandoff.webcamStream) with the same constraints used in
the fresh-capture branch (e.g. width:1280, height:720, frameRate:30), await it
and catch errors so you can fall back to the original track if applyConstraints
fails; adjust references to webcamStream.current and the code that starts the
recorder so it uses the upgraded track.

---

Nitpick comments:
In `@electron/ipc/handlers.ts`:
- Around line 221-224: The get-session-type IPC handler returns
process.env.XDG_SESSION_TYPE directly which may be unexpected values; update the
ipcMain.handle("get-session-type", ...) implementation to normalize the session
type to only "wayland" or "x11": read XDG_SESSION_TYPE (and fall back to
checking WAYLAND_DISPLAY as before), lowercase it, then return "wayland" if it
equals "wayland" and return "x11" for any other value. Ensure you update the
logic inside the ipcMain.handle callback so consumers always receive one of the
two known values.

In `@src/components/launch/LaunchWindow.tsx`:
- Around line 145-172: prevSourceId is declared inside the useEffect so it
resets whenever the effect re-runs causing duplicate startPreview calls; move
that state to a ref (e.g., prevSourceIdRef = useRef<string | null>(null)) at
component scope and replace uses of prevSourceId in the effect with
prevSourceIdRef.current, updating prevSourceIdRef.current = source.id after
calling startPreview(source.id) and when selecting a source so the previous
source id persists across effect runs; keep the rest of the checkSelectedSource
logic and the interval handling the same.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb9512f1-35fc-4232-8232-fa7c1dd78fd9

📥 Commits

Reviewing files that changed from the base of the PR and between b101820 and c69debc.

📒 Files selected for processing (10)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/components/launch/LaunchWindow.tsx
  • src/components/launch/LivePreview.tsx
  • src/hooks/usePreviewStream.ts
  • src/hooks/useScreenRecorder.ts
  • src/vite-env.d.ts

Enable WebRTCPipeWireCapturer and ozone-platform-hint flags on Linux
to support screen capture via PipeWire on Wayland sessions.
Expand from 500x155 fixed pill bar to 480x420 resizable window
(380-640 width range) to accommodate the live preview area.
Position bottom-right instead of bottom-center.
Add get-session-type IPC handler to detect display server type
on Linux, enabling Wayland-aware source selection in the renderer.
Manages the MediaStream lifecycle for screen capture preview:
- Starts/stops preview streams with source switching support
- Handles webcam stream alongside screen capture
- Supports stream detachment for seamless handoff to MediaRecorder
  (avoids double getUserMedia calls and Wayland re-prompts)
Real-time canvas-based preview that composites screen capture with
a circular webcam PiP overlay. Renders at 30fps with throttling,
caps internal resolution at 960px for GPU efficiency. Shows a
placeholder when no source is selected.
Accept optional PreviewStreamHandoff in toggleRecording/startRecording
to reuse existing preview MediaStreams instead of creating new ones.
This avoids double getUserMedia calls and PipeWire re-prompts on Wayland.
When a handoff is provided, video constraints are upgraded in-place.
Replace the 500x155 HUD pill bar with a full preview window featuring:
- Live screen capture preview that starts when a source is selected
- Canvas-composited webcam PiP overlay in the preview
- Recording indicator in the title bar
- Stream handoff from preview to recorder (no double getUserMedia)
- Auto-restart preview after recording stops
- Glass-morphism container styling
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/launch/LaunchWindow.tsx (1)

187-214: ⚠️ Potential issue | 🟠 Major

Persist prevSourceId across effect reruns.

Line 189 recreates prevSourceId every time recording changes, so the same source looks “new” again after each stop. In the current flow that can call startPreview(source.id) once from the polling effect and again from the 500 ms restart path, which risks duplicate capture restarts and extra portal prompts.

💡 Proposed fix
-import { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
...
+	const prevSourceIdRef = useRef<string | null>(null);
...
 	useEffect(() => {
-		let prevSourceId: string | null = null;
-
 		const checkSelectedSource = async () => {
 			if (window.electronAPI) {
 				const source = await window.electronAPI.getSelectedSource();
 				if (source) {
 					setSelectedSource(source.name);
 					setHasSelectedSource(true);

-					if (source.id !== prevSourceId && !recording) {
-						prevSourceId = source.id;
+					if (!recording && source.id !== prevSourceIdRef.current) {
+						prevSourceIdRef.current = source.id;
 						startPreview(source.id);
 					}
 				} else {
 					setSelectedSource("Screen");
 					setHasSelectedSource(false);
+					prevSourceIdRef.current = null;
 				}
 			}
 		};

Also applies to: 222-231

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/LaunchWindow.tsx` around lines 187 - 214, The bug is
that prevSourceId is reinitialized inside the useEffect on every rerun
(dependent on recording/startPreview), causing the same source to be treated as
new; move prevSourceId out of the effect and persist it across renders (e.g.,
use a React ref or component-level state) so checkSelectedSource can compare
against the previous value consistently; update references to prevSourceId in
the checkSelectedSource function and ensure cleanup/interval logic remains the
same so startPreview(source.id) is only called when the actual source id
changes.
♻️ Duplicate comments (1)
src/hooks/useScreenRecorder.ts (1)

456-459: ⚠️ Potential issue | 🟠 Major

Upgrade the handed-off webcam track before starting webcamRecorder.

Lines 456-459 still reuse the preview webcam stream as-is, so handoff recordings can fall back to preview quality even though the fresh-capture branch requests 1280×720 at 30 fps. Apply the same best-effort applyConstraints(...) upgrade on the reused track before creating webcamRecorder.

💡 Proposed fix
 			if (previewHandoff?.webcamStream) {
 				// Reuse preview webcam stream
 				webcamStream.current = previewHandoff.webcamStream;
+				const webcamTrack = webcamStream.current.getVideoTracks()[0];
+				if (webcamTrack) {
+					try {
+						await webcamTrack.applyConstraints({
+							width: { ideal: WEBCAM_TARGET_WIDTH },
+							height: { ideal: WEBCAM_TARGET_HEIGHT },
+							frameRate: {
+								ideal: WEBCAM_TARGET_FRAME_RATE,
+								max: WEBCAM_TARGET_FRAME_RATE,
+							},
+						});
+					} catch {
+						// Best-effort upgrade; keep preview settings if unsupported.
+					}
+				}
 			} else if (webcamEnabled) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 456 - 459, When reusing a
handed-off preview stream (previewHandoff.webcamStream) we must attempt to
upgrade its track constraints to match the fresh-capture settings before
creating the webcamRecorder; modify the branch that currently sets
webcamStream.current = previewHandoff.webcamStream so that you get the
MediaStreamTrack from previewHandoff.webcamStream (e.g., getVideoTracks()[0])
and call track.applyConstraints({ width: 1280, height: 720, frameRate: 30 }) in
a best-effort try/catch, falling back silently on failure, then set
webcamStream.current to the (possibly-upgraded) stream and proceed to create
webcamRecorder as in the fresh-capture path; reference previewHandoff,
webcamStream, webcamEnabled, webcamRecorder and applyConstraints when making
this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 360-374: The select currently uses undefined names
(selectedDeviceId, setSelectedDeviceId, devices); update it to use the names
returned by useMicrophoneDevices: replace selectedDeviceId with selectedMicId,
setSelectedDeviceId with setSelectedMicId, and devices with micDevices, while
keeping the existing microphoneDeviceId and setMicrophoneDeviceId logic; ensure
the select value uses (microphoneDeviceId || selectedMicId), onchange calls
setSelectedMicId and setMicrophoneDeviceId, and the option list maps micDevices
(keying by device.deviceId and showing device.label) so the component
(LaunchWindow and the useMicrophoneDevices hook usage) compiles.
- Around line 384-492: The JSX control-bar has unbalanced tags causing a parse
error: remove the stray closing </button> after the restartRecording Tooltip and
the extraneous `)}` after the openVideoFile Tooltip, then rewrap the conditional
blocks so the outer divs and fragments are balanced; specifically, ensure the
block that renders the Record/Stop button, the conditional {recording &&
<Tooltip>...restartRecording...</Tooltip>} and the subsequent Tooltip buttons
(openVideoFile -> openProjectFile) are siblings inside the same container div,
and that you keep Tooltip components (and their inner buttons using
hudIconBtnClasses, openVideoFile, openProjectFile, restartRecording) properly
opened and closed with matching JSX tags and no leftover fragments or
parentheses.

---

Outside diff comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 187-214: The bug is that prevSourceId is reinitialized inside the
useEffect on every rerun (dependent on recording/startPreview), causing the same
source to be treated as new; move prevSourceId out of the effect and persist it
across renders (e.g., use a React ref or component-level state) so
checkSelectedSource can compare against the previous value consistently; update
references to prevSourceId in the checkSelectedSource function and ensure
cleanup/interval logic remains the same so startPreview(source.id) is only
called when the actual source id changes.

---

Duplicate comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 456-459: When reusing a handed-off preview stream
(previewHandoff.webcamStream) we must attempt to upgrade its track constraints
to match the fresh-capture settings before creating the webcamRecorder; modify
the branch that currently sets webcamStream.current =
previewHandoff.webcamStream so that you get the MediaStreamTrack from
previewHandoff.webcamStream (e.g., getVideoTracks()[0]) and call
track.applyConstraints({ width: 1280, height: 720, frameRate: 30 }) in a
best-effort try/catch, falling back silently on failure, then set
webcamStream.current to the (possibly-upgraded) stream and proceed to create
webcamRecorder as in the fresh-capture path; reference previewHandoff,
webcamStream, webcamEnabled, webcamRecorder and applyConstraints when making
this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b19ee7d9-96d0-415c-b086-b45a59d325af

📥 Commits

Reviewing files that changed from the base of the PR and between c69debc and 90539bf.

📒 Files selected for processing (2)
  • src/components/launch/LaunchWindow.tsx
  • src/hooks/useScreenRecorder.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/launch/LaunchWindow.tsx (1)

246-253: ⚠️ Potential issue | 🟡 Minor

missing null check on window.electronAPI — inconsistent with other calls.

you're using optional chaining in sendHudOverlayHide (line 262) and sendHudOverlayClose (line 269), but openVideoFile just yolo calls window.electronAPI.openVideoFilePicker(). if electronAPI is undefined this will throw.

nit: quick fix
 const openVideoFile = async () => {
+	if (!window.electronAPI) return;
 	const result = await window.electronAPI.openVideoFilePicker();

or use optional chaining with early return if undefined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/LaunchWindow.tsx` around lines 246 - 253, The
openVideoFile function calls window.electronAPI.openVideoFilePicker() without
guarding for undefined; add the same null-check pattern used by
sendHudOverlayHide/sendHudOverlayClose: first verify window.electronAPI exists
(or use optional chaining) and early-return if missing, then call
openVideoFilePicker(), and proceed to call setCurrentVideoPath and
switchToEditor only when electronAPI and returned values are present (e.g.,
check result.success and result.path as already done).
src/hooks/useScreenRecorder.ts (1)

47-62: ⚠️ Potential issue | 🔴 Critical

togglePaused is returned but never defined — this crashes.

kinda cursed: you're returning togglePaused at line 724 but it literally doesn't exist anywhere in this file. you have setPaused from useState at line 94 but no toggle function. this is either a compile error or a runtime ReferenceError waiting to happen.

also the UseScreenRecorderReturn type (lines 47-62) is missing paused, elapsedSeconds, and togglePaused that you're actually returning — type doesn't match implementation.

two fixes needed:

  1. add the missing togglePaused function, or
  2. if pause isn't needed, remove it from the return and update the type

LaunchWindow already destructures togglePaused at line 83, so something needs to be returned here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useScreenRecorder.ts` around lines 47 - 62, The hook currently
returns togglePaused but never defines it and the UseScreenRecorderReturn type
is missing paused, elapsedSeconds, and togglePaused; add a togglePaused function
that flips the existing setPaused state (from useState) and returns the new
paused value (e.g., const togglePaused = () => { setPaused(p => { const next =
!p; return next }); return /* new value */ }), include paused and elapsedSeconds
in the UseScreenRecorderReturn type, and ensure the hook's returned object
includes the newly defined togglePaused, paused, and elapsedSeconds so
LaunchWindow (which destructures togglePaused) keeps working.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@electron/ipc/handlers.ts`:
- Around line 359-362: The "get-session-type" IPC handler currently returns raw
XDG_SESSION_TYPE which can be unpredictable; update the
ipcMain.handle("get-session-type") implementation to canonicalize and constrain
output to exactly "wayland" or "x11": on non-Linux keep returning "x11", on
Linux read process.env.XDG_SESSION_TYPE and process.env.WAYLAND_DISPLAY,
normalize the env value by trimming and lower-casing, and return "wayland" only
if the normalized XDG_SESSION_TYPE === "wayland" or WAYLAND_DISPLAY is present;
otherwise always return "x11".

In `@src/components/launch/LaunchWindow.tsx`:
- Around line 107-108: The component never updates the local elapsed state and
keeps unused recordingStart state: useScreenRecorder()'s elapsedSeconds
(destructured earlier) should be used or synced to fix the REC timer; replace
usages of the local elapsed state with elapsedSeconds from useScreenRecorder OR
add an effect that calls setElapsed(elapsedSeconds) when elapsedSeconds changes,
and remove the dead recordingStart/setRecordingStart state if it's not needed
(references: useScreenRecorder, elapsedSeconds, elapsed, setElapsed,
recordingStart, setRecordingStart, and the REC timer render locations).

---

Outside diff comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 246-253: The openVideoFile function calls
window.electronAPI.openVideoFilePicker() without guarding for undefined; add the
same null-check pattern used by sendHudOverlayHide/sendHudOverlayClose: first
verify window.electronAPI exists (or use optional chaining) and early-return if
missing, then call openVideoFilePicker(), and proceed to call
setCurrentVideoPath and switchToEditor only when electronAPI and returned values
are present (e.g., check result.success and result.path as already done).

In `@src/hooks/useScreenRecorder.ts`:
- Around line 47-62: The hook currently returns togglePaused but never defines
it and the UseScreenRecorderReturn type is missing paused, elapsedSeconds, and
togglePaused; add a togglePaused function that flips the existing setPaused
state (from useState) and returns the new paused value (e.g., const togglePaused
= () => { setPaused(p => { const next = !p; return next }); return /* new value
*/ }), include paused and elapsedSeconds in the UseScreenRecorderReturn type,
and ensure the hook's returned object includes the newly defined togglePaused,
paused, and elapsedSeconds so LaunchWindow (which destructures togglePaused)
keeps working.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fbba8748-9780-40d5-8cc7-3fbdc2f15242

📥 Commits

Reviewing files that changed from the base of the PR and between 90539bf and 853603d.

📒 Files selected for processing (3)
  • electron/ipc/handlers.ts
  • src/components/launch/LaunchWindow.tsx
  • src/hooks/useScreenRecorder.ts

@FabLrc
Copy link
Copy Markdown
Contributor

FabLrc commented Apr 7, 2026

Hey @hamidlabs, thanks for the PR! I ran tsc --noEmit and found a few issues that need to be fixed before this can be merged:

  • BsPauseCircle and BsPlayCircle are imported but don't exist (removed from the package?)
  • paused, elapsedSeconds, and togglePaused are used in LaunchWindow.tsx but are missing from UseScreenRecorderReturn — also causing errors in useScreenRecorder.ts:721
  • selectedDeviceId / setSelectedDeviceId should be selectedMicId / setSelectedMicId
  • devices is undefined in the mic controls panel (likely a rename from useMicrophoneDevices)
  • Several declared variables are unused: cancelRecording, recordingStart, setElapsed, showWebcamControls, and a few others

Also had two JSX structural bugs (mismatched tags) that prevented the app from compiling at all.

Could you take a look and update accordingly? Happy to help if anything is unclear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants